package text

import (
	"image"
	"time"

	"code.google.com/p/jnj/graphics"
)

const dclickpause = 500 * time.Millisecond

const (
	b1 = 1 << iota
	b2
	b3
	b4 // mouse wheel up
	b5 // mouse wheel down

	// state
	nostate = iota
	scrolling
	sweeping
)

func (b *Buffer) domouse() {
	var (
		pos, oldpos   image.Point
		result        *Address
		state         int
		btns, oldbtns int
	)
	b.dclicktimer = &time.Timer{}
	for {
		result = nil
		ev := <-b.MouseChan
		// TODO why are we getting uints? this shouldn't be necessary.
		ev.X, ev.Y = int(int32(ev.X)), int(int32(ev.Y))

		pos = b.getpos(ev.Point)
		btns = ev.Buttons

		// special handling if the user is sweeping or dragging the scrollbar
		regbtn := btns == b1 || btns == b2 || btns == b3
		if state == nostate && ev.Point.In(b.scrollbar.Bounds()) && regbtn {
			state = scrolling
		} else if state == nostate && regbtn {
			state = sweeping
		}
		if state == scrolling {
			if regbtn {
				b.scrollbar.Event(ev)
				if b.scrollbar.Dirty {
					b.redraw()
				}
			} else {
				state = nostate
			}
			oldbtns = btns
			b.ClickChan <- nil
			continue
		}

		if btns == b1 || btns == b2 || btns == b3 {
			if oldbtns > btns {
				diff := oldbtns ^ btns
				result = b.release(diff, pos)
			} else if oldbtns == btns {
				b.sweep(ev, pos, oldpos)
			} else {
				result = b.click(btns, pos, oldpos)
			}
		}
		if btns == b1|b2 || btns == b1|b3 {
			b.click(btns, pos, oldpos)
		}
		if btns == b4 || btns == b5 {
			switch btns {
			case b4:
				b.scroll(-1)
			case b5:
				b.scroll(1)
			}
			b.redraw()
		}
		if btns == 0 {
			if oldbtns != 0 {
				diff := oldbtns ^ btns
				result = b.release(diff, pos)
			}
			state = nostate
		} else {
			oldpos = pos
		}
		oldbtns = btns
		b.ClickChan <- result
	}
}

func (b *Buffer) sweep(ev *graphics.MouseEvent, pos, oldpos image.Point) {
	if pos != oldpos {
		// seldiff is used as draw bounds when sweeping, to avoid redrawing
		// the whole buffer.
		seldiff := b.r
		if oldpos.Y < pos.Y {
			seldiff.Min.Y = b.getypx(oldpos.Y)
			seldiff.Max.Y = b.getypx(pos.Y + 1)
		} else {
			seldiff.Min.Y = b.getypx(pos.Y)
			seldiff.Max.Y = b.getypx(oldpos.Y + 1)
		}

		b.selto(pos)

		// scroll when mouse is outside of window. forget seldiff, redraw everything.
		if ev.Y < b.r.Min.Y {
			diff := ev.Y - b.r.Min.Y
			b.scroll(diff / b.font.Height)
			b.redraw()
		} else if ev.Y > b.r.Max.Y {
			diff := ev.Y - b.r.Max.Y
			b.scroll(diff / b.font.Height)
			b.redraw()
		} else {
			b.redrawsel(seldiff)
		}
	}
}

func (b *Buffer) click(button int, pos, oldpos image.Point) *Address {
	var result *Address
	switch button {
	case b1:
		b.setpos(pos)
		if b.dclick == true && pos == oldpos {
			b.doubleclick(pos)
			b.dclick = false
			b.dclicktimer.Stop()
		} else {
			b.dclick = true
			b.dclicktimer.Stop()
			b.dclicktimer = time.AfterFunc(dclickpause, func() { b.dclick = false })
		}

	case b2:
		b.setpos(pos)

	case b3:
		// only set a new dot if they click outside of the selection rect.
		r := image.Rectangle{b.Dot.Left, b.Dot.Right}
		r.Max.Y++
		if pos.In(r) == false {
			b.setpos(pos)
		}

	case b1 | b2:
		s := b.getsel()
		result = &b.Dot
		b.snarf(s)
		b.deletesel()

	case b1 | b3:
		b.paste()
		result = &b.Dot
	}

	// every click event is a redraw.
	b.redraw()

	return result
}

func (b *Buffer) release(released int, pos image.Point) *Address {
	var result *Address
	if released&(b2|b3) > 0 && b.Dot.Left == b.Dot.Right {
		// in some situations, if there is no selection, return the word clicked on
		result = b.getword(pos)
	} else {
		// otherwise return the current selection or position
		result = &b.Dot
	}
	return result
}

func (b *Buffer) setpos(pos image.Point) {
	if b.Dot.selection == selNone && pos == b.Dot.Left {
		return
	}
	b.Dot.selection = selNone
	b.Dot.Left = pos
	b.Dot.Right = b.Dot.Left
}

func (b *Buffer) getpos(pt image.Point) image.Point {
	pt = pt.Add(b.scrollpt).Sub(b.img.Rect.Min)
	// (0,0) if pt is above the buffer
	if pt.Y < 0 {
		return image.ZP
	}

	var pos image.Point
	pos.Y = pt.Y / b.font.Height

	// end of the last line if pos is below the last line
	if pos.Y > len(b.lines)-1 {
		pos.Y = len(b.lines) - 1
		pos.X = len(b.lines[pos.Y].s)
		return pos
	}

	if pt.X < 0 {
		return image.Pt(0, pos.Y)
	}

	line := b.lines[pos.Y]
	for i := range line.px {
		if line.px[i]+b.r.Min.X > pt.X {
			if i > 0 {
				pos.X = i - 1
			}
			return pos
		}
	}
	pos.X = len(line.s)
	return pos
}
